<!DOCTYPE html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<meta name="referrer" content="same-origin">
<meta name="referrer" content="no-referrer" />








<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="nacos," />










<meta name="description" content="&amp;emsp;&amp;emsp;项目中使用nacos作为配置中心，在使用的时候发现一个问题，nacos手动修改配置但是变更总是收不到通知，这段代码如下：123456789101112131415161718192021@Configurationpublic class LogbackConfig &amp;#123;    private volatile String logLevel;    @NacosV">
<meta name="keywords" content="nacos">
<meta property="og:type" content="article">
<meta property="og:title" content="记一次nacos刷新失败排查">
<meta property="og:url" content="http://yoursite.com/2020/11/29/记一次nacos刷新失败排查/index.html">
<meta property="og:site_name" content="矩阵编程">
<meta property="og:description" content="&amp;emsp;&amp;emsp;项目中使用nacos作为配置中心，在使用的时候发现一个问题，nacos手动修改配置但是变更总是收不到通知，这段代码如下：123456789101112131415161718192021@Configurationpublic class LogbackConfig &amp;#123;    private volatile String logLevel;    @NacosV">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2020-11-29T12:46:00.406Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="记一次nacos刷新失败排查">
<meta name="twitter:description" content="&amp;emsp;&amp;emsp;项目中使用nacos作为配置中心，在使用的时候发现一个问题，nacos手动修改配置但是变更总是收不到通知，这段代码如下：123456789101112131415161718192021@Configurationpublic class LogbackConfig &amp;#123;    private volatile String logLevel;    @NacosV">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://yoursite.com/2020/11/29/记一次nacos刷新失败排查/"/>





  <title>记一次nacos刷新失败排查 | 矩阵编程</title>
  





  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?7d29e6fba8e07d1a8af2414a20e5f2a6";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">矩阵编程</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-主页">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-主页"></i> <br />
            
            主页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-分类"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-所有文章">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-文章"></i> <br />
            
            所有文章
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://yoursite.com/2020/11/29/记一次nacos刷新失败排查/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="matrix">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="矩阵编程">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">记一次nacos刷新失败排查</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2020-11-29T20:46:00+08:00">
                2020-11-29
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/nacos/" itemprop="url" rel="index">
                    <span itemprop="name">nacos</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2020/11/29/记一次nacos刷新失败排查/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count"
                        data-disqus-identifier="2020/11/29/记一次nacos刷新失败排查/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>&emsp;&emsp;项目中使用nacos作为配置中心，在使用的时候发现一个问题，nacos手动修改配置但是变更总是收不到通知，这段代码如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LogbackConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> String logLevel;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@NacosValue</span>(value = <span class="string">"$&#123;logging.level.root:null&#125;"</span>, autoRefreshed = <span class="keyword">true</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setLogLevel</span><span class="params">(String level)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (ObjectUtils.isEmpty(level)</span><br><span class="line">                || <span class="string">"null"</span>.equals(level)) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (level.equals(<span class="keyword">this</span>.logLevel)) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.logLevel = level;</span><br><span class="line">        ch.qos.logback.classic.LoggerContext loggerContext</span><br><span class="line">                = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory();</span><br><span class="line">        Logger logger = loggerContext.getLogger(<span class="string">"root"</span>);</span><br><span class="line">        logger.setLevel(ch.qos.logback.classic.Level.toLevel(level));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这段代码的目的是期望logging.level.root有变动的时候，nacos能够主动通知调用<strong>setLogLevel</strong>方法来变更日志，很简单的功能，但是实际运行的现象很奇怪：</p>
<ol>
<li>在项目启动的时候，发现<strong>setLogLevel</strong>这个方法是能够调用并设值的。</li>
<li>手动更新了nacos里面的配置，第一次修改<strong>setLogLevel</strong>能够调用，但是level是老的值。</li>
<li>只有第一次修改<strong>setLogLevel</strong>能够被调用，后面无论修改多少次这个方法不再触发。</li>
</ol>
<a id="more"></a>
<h3 id="nacos源码"><a href="#nacos源码" class="headerlink" title="nacos源码"></a>nacos源码</h3><p>&emsp;&emsp;大致看了下nacos的代码，从<strong>ClientWorker</strong>开始说起，nacos client使用<strong>LongPollingRunnable</strong>拉取服务端的配置，这个类不是很复杂所以直接贴代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LongPollingRunnable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> taskId;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">LongPollingRunnable</span><span class="params">(<span class="keyword">int</span> taskId)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">this</span>.taskId = taskId;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">            List&lt;CacheData&gt; cacheDatas = <span class="keyword">new</span> ArrayList&lt;CacheData&gt;();</span><br><span class="line">            List&lt;String&gt; inInitializingCacheList = <span class="keyword">new</span> ArrayList&lt;String&gt;();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// check failover config</span></span><br><span class="line">                <span class="keyword">for</span> (CacheData cacheData : cacheMap.get().values()) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (cacheData.getTaskId() == taskId) &#123;</span><br><span class="line">                        cacheDatas.add(cacheData);</span><br><span class="line">                        <span class="keyword">try</span> &#123;</span><br><span class="line">                            checkLocalConfig(cacheData);</span><br><span class="line">                            <span class="keyword">if</span> (cacheData.isUseLocalConfigInfo()) &#123;</span><br><span class="line">                                cacheData.checkListenerMd5();</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                            LOGGER.error(<span class="string">"get local config info error"</span>, e);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// check server config</span></span><br><span class="line">                List&lt;String&gt; changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);</span><br><span class="line">                LOGGER.info(<span class="string">"get changedGroupKeys:"</span> + changedGroupKeys);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">for</span> (String groupKey : changedGroupKeys) &#123;</span><br><span class="line">                    String[] key = GroupKey.parseKey(groupKey);</span><br><span class="line">                    String dataId = key[<span class="number">0</span>];</span><br><span class="line">                    String group = key[<span class="number">1</span>];</span><br><span class="line">                    String tenant = <span class="keyword">null</span>;</span><br><span class="line">                    <span class="keyword">if</span> (key.length == <span class="number">3</span>) &#123;</span><br><span class="line">                        tenant = key[<span class="number">2</span>];</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        String[] ct = getServerConfig(dataId, group, tenant, <span class="number">3000L</span>);</span><br><span class="line">                        CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));</span><br><span class="line">                        cache.setContent(ct[<span class="number">0</span>]);</span><br><span class="line">                        <span class="keyword">if</span> (<span class="keyword">null</span> != ct[<span class="number">1</span>]) &#123;</span><br><span class="line">                            cache.setType(ct[<span class="number">1</span>]);</span><br><span class="line">                        &#125;</span><br><span class="line">                        LOGGER.info(<span class="string">"[&#123;&#125;] [data-received] dataId=&#123;&#125;, group=&#123;&#125;, tenant=&#123;&#125;, md5=&#123;&#125;, content=&#123;&#125;, type=&#123;&#125;"</span>,</span><br><span class="line">                            agent.getName(), dataId, group, tenant, cache.getMd5(),</span><br><span class="line">                            ContentUtils.truncateContent(ct[<span class="number">0</span>]), ct[<span class="number">1</span>]);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (NacosException ioe) &#123;</span><br><span class="line">                        String message = String.format(</span><br><span class="line">                            <span class="string">"[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s"</span>,</span><br><span class="line">                            agent.getName(), dataId, group, tenant);</span><br><span class="line">                        LOGGER.error(message, ioe);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">for</span> (CacheData cacheData : cacheDatas) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (!cacheData.isInitializing() || inInitializingCacheList</span><br><span class="line">                        .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) &#123;</span><br><span class="line">                        cacheData.checkListenerMd5();</span><br><span class="line">                        cacheData.setInitializing(<span class="keyword">false</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                inInitializingCacheList.clear();</span><br><span class="line"></span><br><span class="line">                executorService.execute(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// If the rotation training task is abnormal, the next execution time of the task will be punished</span></span><br><span class="line">                LOGGER.error(<span class="string">"longPolling error : "</span>, e);</span><br><span class="line">                executorService.schedule(<span class="keyword">this</span>, taskPenaltyTime, TimeUnit.MILLISECONDS);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></p>
<ol>
<li><strong>CacheData</strong> 配置的数据结构，包含了配置的监听者、内容、DataID、Group等。</li>
<li><strong>checkLocalConfig</strong> 检测本地failoverFile文件，根据<a href="https://github.com/alibaba/nacos/issues/4138" target="_blank" rel="noopener">The config questions of nacos</a>这个issue的描述，failoverFile需要用户定义，所以暂且忽略。</li>
<li><strong>checkUpdateDataIds</strong> 从Server获取值变化了的DataID列表。</li>
<li>遍历有变化的DataID列表，<strong>getServerConfig</strong> 从nacos服务器上获取对应的配置文件。</li>
<li>如果某个DataID的<strong>CacheData</strong>有变动，调用<strong>CacheData.checkListenerMd5</strong>，比对上次内容的MD5值，如果不一致则遍历通知监听者。</li>
</ol>
<p>贴一下<strong>CacheData.checkListenerMd5</strong>这部分的代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">checkListenerMd5</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (ManagerListenerWrap wrap : listeners) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!md5.equals(wrap.lastCallMd5)) &#123;</span><br><span class="line">            safeNotifyListener(dataId, group, content, type, md5, wrap);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">safeNotifyListener</span><span class="params">(<span class="keyword">final</span> String dataId, <span class="keyword">final</span> String group, <span class="keyword">final</span> String content, <span class="keyword">final</span> String type,</span></span></span><br><span class="line"><span class="function"><span class="params">                                <span class="keyword">final</span> String md5, <span class="keyword">final</span> ManagerListenerWrap listenerWrap)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Listener listener = listenerWrap.listener;</span><br><span class="line"></span><br><span class="line">    Runnable job = <span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();</span><br><span class="line">            ClassLoader appClassLoader = listener.getClass().getClassLoader();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (listener <span class="keyword">instanceof</span> AbstractSharedListener) &#123;</span><br><span class="line">                    AbstractSharedListener adapter = (AbstractSharedListener) listener;</span><br><span class="line">                    adapter.fillContext(dataId, group);</span><br><span class="line">                    LOGGER.info(<span class="string">"[&#123;&#125;] [notify-context] dataId=&#123;&#125;, group=&#123;&#125;, md5=&#123;&#125;"</span>, name, dataId, group, md5);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 执行回调之前先将线程classloader设置为具体webapp的classloader，以免回调方法中调用spi接口是出现异常或错用（多应用部署才会有该问题）。</span></span><br><span class="line">                Thread.currentThread().setContextClassLoader(appClassLoader);</span><br><span class="line"></span><br><span class="line">                ConfigResponse cr = <span class="keyword">new</span> ConfigResponse();</span><br><span class="line">                cr.setDataId(dataId);</span><br><span class="line">                cr.setGroup(group);</span><br><span class="line">                cr.setContent(content);</span><br><span class="line">                configFilterChainManager.doFilter(<span class="keyword">null</span>, cr);</span><br><span class="line">                String contentTmp = cr.getContent();</span><br><span class="line">                listener.receiveConfigInfo(contentTmp);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// compare lastContent and content</span></span><br><span class="line">                <span class="keyword">if</span> (listener <span class="keyword">instanceof</span> AbstractConfigChangeListener) &#123;</span><br><span class="line">                    Map data = ConfigChangeHandler.getInstance().parseChangeData(listenerWrap.lastContent, content, type);</span><br><span class="line">                    ConfigChangeEvent event = <span class="keyword">new</span> ConfigChangeEvent(data);</span><br><span class="line">                    ((AbstractConfigChangeListener)listener).receiveConfigChange(event);</span><br><span class="line">                    listenerWrap.lastContent = content;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                listenerWrap.lastCallMd5 = md5;</span><br><span class="line">                LOGGER.info(<span class="string">"[&#123;&#125;] [notify-ok] dataId=&#123;&#125;, group=&#123;&#125;, md5=&#123;&#125;, listener=&#123;&#125; "</span>, name, dataId, group, md5,</span><br><span class="line">                    listener);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (NacosException de) &#123;</span><br><span class="line">                LOGGER.error(<span class="string">"[&#123;&#125;] [notify-error] dataId=&#123;&#125;, group=&#123;&#125;, md5=&#123;&#125;, listener=&#123;&#125; errCode=&#123;&#125; errMsg=&#123;&#125;"</span>, name,</span><br><span class="line">                    dataId, group, md5, listener, de.getErrCode(), de.getErrMsg());</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">                LOGGER.error(<span class="string">"[&#123;&#125;] [notify-error] dataId=&#123;&#125;, group=&#123;&#125;, md5=&#123;&#125;, listener=&#123;&#125; tx=&#123;&#125;"</span>, name, dataId, group,</span><br><span class="line">                    md5, listener, t.getCause());</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                Thread.currentThread().setContextClassLoader(myClassLoader);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">long</span> startNotify = System.currentTimeMillis();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">null</span> != listener.getExecutor()) &#123;</span><br><span class="line">            listener.getExecutor().execute(job);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            job.run();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">        LOGGER.error(<span class="string">"[&#123;&#125;] [notify-error] dataId=&#123;&#125;, group=&#123;&#125;, md5=&#123;&#125;, listener=&#123;&#125; throwable=&#123;&#125;"</span>, name, dataId, group,</span><br><span class="line">            md5, listener, t.getCause());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">long</span> finishNotify = System.currentTimeMillis();</span><br><span class="line">    LOGGER.info(<span class="string">"[&#123;&#125;] [notify-listener] time cost=&#123;&#125;ms in ClientWorker, dataId=&#123;&#125;, group=&#123;&#125;, md5=&#123;&#125;, listener=&#123;&#125; "</span>,</span><br><span class="line">        name, (finishNotify - startNotify), dataId, group, md5, listener);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这里只看一句关键性的代码<strong>listener.receiveConfigInfo(contentTmp)</strong>，调用监听者并返回整个配置内容。这里的listener包含了用户自定义的listener以及nacos设定的listener，比如：<strong>DelegatingEventPublishingListener</strong>。<strong>DelegatingEventPublishingListener</strong>看名字即知是一个代理类，贴一下它监听的代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveConfigInfo</span><span class="params">(String content)</span> </span>&#123;</span><br><span class="line">	onReceived(content);</span><br><span class="line">	publishEvent(content);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">publishEvent</span><span class="params">(String content)</span> </span>&#123;</span><br><span class="line">	NacosConfigReceivedEvent event = <span class="keyword">new</span> NacosConfigReceivedEvent(configService,</span><br><span class="line">			dataId, groupId, content, configType);</span><br><span class="line">	applicationEventPublisher.publishEvent(event);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">onReceived</span><span class="params">(String content)</span> </span>&#123;</span><br><span class="line">	delegate.receiveConfigInfo(content);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这里的delegate其实是<strong>NacosPropertySourcePostProcessor</strong>内部的一个listener，一个匿名内部类：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Listener listener = <span class="keyword">new</span> AbstractListener() &#123;</span><br><span class="line"></span><br><span class="line">				<span class="meta">@Override</span></span><br><span class="line">				<span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveConfigInfo</span><span class="params">(String config)</span> </span>&#123;</span><br><span class="line">					String name = nacosPropertySource.getName();</span><br><span class="line">					NacosPropertySource newNacosPropertySource = <span class="keyword">new</span> NacosPropertySource(</span><br><span class="line">							dataId, groupId, name, config, type);</span><br><span class="line">					newNacosPropertySource.copy(nacosPropertySource);</span><br><span class="line">					MutablePropertySources propertySources = environment</span><br><span class="line">							.getPropertySources();</span><br><span class="line">					<span class="comment">// replace NacosPropertySource</span></span><br><span class="line">					propertySources.replace(name, newNacosPropertySource);</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;;</span><br></pre></td></tr></table></figure></p>
<p>这里是更新spring property的逻辑，那么<strong>DelegatingEventPublishingListener</strong>其实做了两件事：</p>
<ol>
<li>先调用property监听更新property。</li>
<li>发送NacosConfigReceivedEvent事件。</li>
</ol>
<p>按图索骥，接收<strong>NacosConfigReceivedEvent</strong>事件的只有一个类<strong>NacosValueAnnotationBeanPostProcessor</strong>，贴一下接收事件的代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onApplicationEvent</span><span class="params">(NacosConfigReceivedEvent event)</span> </span>&#123;</span><br><span class="line">		<span class="comment">// In to this event receiver, the environment has been updated the</span></span><br><span class="line">		<span class="comment">// latest configuration information, pull directly from the environment</span></span><br><span class="line">		<span class="comment">// fix issue #142</span></span><br><span class="line">		<span class="keyword">for</span> (Map.Entry&lt;String, List&lt;NacosValueTarget&gt;&gt; entry : placeholderNacosValueTargetMap.entrySet()) &#123;</span><br><span class="line">			String key = environment.resolvePlaceholders(entry.getKey());</span><br><span class="line">			String newValue = environment.getProperty(key);</span><br><span class="line">			<span class="keyword">if</span> (newValue == <span class="keyword">null</span>) &#123;</span><br><span class="line">				<span class="keyword">continue</span>;</span><br><span class="line">			&#125;</span><br><span class="line">			List&lt;NacosValueTarget&gt; beanPropertyList = entry.getValue();</span><br><span class="line">			<span class="keyword">for</span> (NacosValueTarget target : beanPropertyList) &#123;</span><br><span class="line">				String md5String = MD5.getInstance().getMD5String(newValue);</span><br><span class="line">				<span class="keyword">boolean</span> isUpdate = !target.lastMD5.equals(md5String);</span><br><span class="line">				<span class="keyword">if</span> (isUpdate) &#123;</span><br><span class="line">					target.updateLastMD5(md5String);</span><br><span class="line">					<span class="keyword">if</span> (target.method == <span class="keyword">null</span>) &#123;</span><br><span class="line">						setField(target, newValue);</span><br><span class="line">					&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">						setMethod(target, newValue);</span><br><span class="line">					&#125;</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></p>
<p>这里<strong>placeholderNacosValueTargetMap</strong>为<strong>NacosValueTarget</strong>的集合，<strong>NacosValueTarget</strong>是带NacosValue注解的方法、字段或者bean的封装。比如我把NacosValue标注在方法上，那么会调用<strong>setMethod</strong>来调用带注解的方法。而传入这个方法的参数，是从spring property中获取的。最后会检测从property获取的newValue与原有的值进行md5比对，如果发现有变更，才会调用用户的方法。</p>
<h3 id="多版本"><a href="#多版本" class="headerlink" title="多版本"></a>多版本</h3><p>&emsp;&emsp;以上是整个nacos配置变更拉取的步骤，按照上面的现象来说，主要是<strong>NacosValueAnnotationBeanPostProcessor</strong>从spring property中获取的newValue无变化导致。在我调试的过程中发现这个项目依赖的nacos有多个版本，并且我在nacos-spring-context-0.2.2-RC1这个版本中发现<strong>DelegatingEventPublishingListener</strong>的代码好像和其他版本有出入，<strong>receiveConfigInfo</strong>是先发送通知再更新spring property，如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">receiveConfigInfo</span><span class="params">(String content)</span> </span>&#123;</span><br><span class="line">        publishEvent(content);</span><br><span class="line">        onReceived(content);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">publishEvent</span><span class="params">(String content)</span> </span>&#123;</span><br><span class="line">        NacosConfigReceivedEvent event = <span class="keyword">new</span> NacosConfigReceivedEvent(configService, dataId, groupId, content);</span><br><span class="line">        applicationEventPublisher.publishEvent(event);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">onReceived</span><span class="params">(String content)</span> </span>&#123;</span><br><span class="line">        delegate.receiveConfigInfo(content);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></p>
<p>主项目依赖的是<strong>nacos-config-spring-boot-starter 0.2.7</strong>版本，nacos-spring-context为0.3.6，猜测应该是其他组件带来的老版本有这个bug，窃喜之，尝试将其他组件中的nacos使用exclusion排除掉，然后再进行测试。但是最后却遗憾地发现还是失败，看来并不是这个问题导致。</p>
<h3 id="jasypt-spring-boot-starter"><a href="#jasypt-spring-boot-starter" class="headerlink" title="jasypt-spring-boot-starter"></a>jasypt-spring-boot-starter</h3><p>&emsp;&emsp;排除掉版本的影响，从接收NacosConfigReceivedEvent这个事件的监听入手，继续看<strong>environment.getProperty(key)</strong>这个方法为什么返回了老的值，实际是执行<strong>PropertySourcesPropertyResolver.getProperty</strong>方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> &lt;T&gt; <span class="function">T <span class="title">getProperty</span><span class="params">(String key, Class&lt;T&gt; targetValueType, <span class="keyword">boolean</span> resolveNestedPlaceholders)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (<span class="keyword">this</span>.propertySources != <span class="keyword">null</span>) &#123;</span><br><span class="line">		<span class="keyword">for</span> (PropertySource&lt;?&gt; propertySource : <span class="keyword">this</span>.propertySources) &#123;</span><br><span class="line">			<span class="keyword">if</span> (logger.isTraceEnabled()) &#123;</span><br><span class="line">				logger.trace(<span class="string">"Searching for key '"</span> + key + <span class="string">"' in PropertySource '"</span> +</span><br><span class="line">						propertySource.getName() + <span class="string">"'"</span>);</span><br><span class="line">			&#125;</span><br><span class="line">			Object value = propertySource.getProperty(key);</span><br><span class="line">			<span class="keyword">if</span> (value != <span class="keyword">null</span>) &#123;</span><br><span class="line">				<span class="keyword">if</span> (resolveNestedPlaceholders &amp;&amp; value <span class="keyword">instanceof</span> String) &#123;</span><br><span class="line">					value = resolveNestedPlaceholders((String) value);</span><br><span class="line">				&#125;</span><br><span class="line">				logKeyFound(key, propertySource, value);</span><br><span class="line">				<span class="keyword">return</span> convertValueIfNecessary(value, targetValueType);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">if</span> (logger.isTraceEnabled()) &#123;</span><br><span class="line">		logger.trace(<span class="string">"Could not find key '"</span> + key + <span class="string">"' in any property source"</span>);</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>看了下<strong>this.propertySources</strong>内部持有一个PropertySource列表，这个列表其中<strong>ConfigurationPropertySourcesPropertySource</strong>是个CGLib的代理对象，还有一些<strong>EncryptableMapPropertySourceWrapper</strong>，这个<strong>EncryptableMapPropertySourceWrapper</strong>是jasypt库中的类，看来和jasypt有点关系。<br>&emsp;&emsp;<a href="https://github.com/ulisesbocchio/jasypt-spring-boot" target="_blank" rel="noopener">jasypt</a>是一款通用的加解密库，看了一下<strong>jasypt-spring-boot-starter</strong>这个库中的spring.factories文件，启动入口是<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ulisesbocchio.jasyptspringboot.JasyptSpringBootAutoConfiguration</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@Import</span>(EnableEncryptablePropertiesConfiguration.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JasyptSpringBootAutoConfiguration</span> </span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@Import</span>(&#123;EncryptablePropertyResolverConfiguration.class, CachingConfiguration.class&#125;)</span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EnableEncryptablePropertiesConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> EnableEncryptablePropertiesBeanFactoryPostProcessor <span class="title">enableEncryptablePropertySourcesPostProcessor</span><span class="params">(<span class="keyword">final</span> ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> EnableEncryptablePropertiesBeanFactoryPostProcessor(environment, converter);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>主要是暴露<strong>EnableEncryptablePropertiesBeanFactoryPostProcessor</strong>，这是一个<strong>BeanFactoryPostProcessor</strong>，代码比较少直接看：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EnableEncryptablePropertiesBeanFactoryPostProcessor</span> <span class="keyword">implements</span> <span class="title">BeanFactoryPostProcessor</span>, <span class="title">Ordered</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Logger LOG = LoggerFactory.getLogger(EnableEncryptablePropertiesBeanFactoryPostProcessor.class);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConfigurableEnvironment environment;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EncryptablePropertySourceConverter converter;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EnableEncryptablePropertiesBeanFactoryPostProcessor</span><span class="params">(ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.environment = environment;</span><br><span class="line">        <span class="keyword">this</span>.converter = converter;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postProcessBeanFactory</span><span class="params">(ConfigurableListableBeanFactory beanFactory)</span> <span class="keyword">throws</span> BeansException </span>&#123;</span><br><span class="line">        LOG.info(<span class="string">"Post-processing PropertySource instances"</span>);</span><br><span class="line">        MutablePropertySources propSources = environment.getPropertySources();</span><br><span class="line">        converter.convertPropertySources(propSources);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getOrder</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Ordered.LOWEST_PRECEDENCE - <span class="number">100</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这个converter是<strong>EncryptablePropertySourceConverter</strong>：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">convertPropertySources</span><span class="params">(MutablePropertySources propSources)</span> </span>&#123;</span><br><span class="line">    StreamSupport.stream(propSources.spliterator(), <span class="keyword">false</span>)</span><br><span class="line">            .filter(ps -&gt; !(ps <span class="keyword">instanceof</span> EncryptablePropertySource))</span><br><span class="line">            .map(<span class="keyword">this</span>::makeEncryptable)</span><br><span class="line">            .collect(toList())</span><br><span class="line">            .forEach(ps -&gt; propSources.replace(ps.getName(), ps));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>主要是创建<strong>PropertySource</strong>并替换到<strong>MutablePropertySources</strong>中，上面<strong>ConfigurationPropertySourcesPropertySource</strong>便是在这里创建的代理，下面只贴一下创建代理的代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> &lt;T&gt; <span class="function">PropertySource&lt;T&gt; <span class="title">proxyPropertySource</span><span class="params">(PropertySource&lt;T&gt; propertySource)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//Silly Chris Beams for making CommandLinePropertySource getProperty and containsProperty methods final. Those methods</span></span><br><span class="line">    <span class="comment">//can't be proxied with CGLib because of it. So fallback to wrapper for Command Line Arguments only.</span></span><br><span class="line">    <span class="keyword">if</span> (CommandLinePropertySource.class.isAssignableFrom(propertySource.getClass())</span><br><span class="line">            <span class="comment">// Other PropertySource classes like org.springframework.boot.env.OriginTrackedMapPropertySource</span></span><br><span class="line">            <span class="comment">// are final classes as well</span></span><br><span class="line">            || Modifier.isFinal(propertySource.getClass().getModifiers())) &#123;</span><br><span class="line">        <span class="keyword">return</span> instantiatePropertySource(propertySource);</span><br><span class="line">    &#125;</span><br><span class="line">    ProxyFactory proxyFactory = <span class="keyword">new</span> ProxyFactory();</span><br><span class="line">    proxyFactory.setTargetClass(propertySource.getClass());</span><br><span class="line">    proxyFactory.setProxyTargetClass(<span class="keyword">true</span>);</span><br><span class="line">    proxyFactory.addInterface(EncryptablePropertySource.class);</span><br><span class="line">    proxyFactory.setTarget(propertySource);</span><br><span class="line">    proxyFactory.addAdvice(<span class="keyword">new</span> EncryptablePropertySourceMethodInterceptor&lt;&gt;(propertySource, propertyResolver, propertyFilter));</span><br><span class="line">    <span class="keyword">return</span> (PropertySource&lt;T&gt;) proxyFactory.getProxy();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这段代码同时也给了我一个使用<a href="/2020/11/26/谈谈Spring的AOP">Spring AOP增强普通类</a>的思路，来看看<strong>EncryptablePropertySourceMethodInterceptor</strong>是如何增强<strong>PropertySource</strong>的：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EncryptablePropertySourceMethodInterceptor</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">CachingDelegateEncryptablePropertySource</span>&lt;<span class="title">T</span>&gt; <span class="keyword">implements</span> <span class="title">MethodInterceptor</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EncryptablePropertySourceMethodInterceptor</span><span class="params">(PropertySource&lt;T&gt; delegate, EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>(delegate, resolver, filter);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(MethodInvocation invocation)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (isRefreshCall(invocation)) &#123;</span><br><span class="line">            refresh();</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (isGetDelegateCall(invocation)) &#123;</span><br><span class="line">            <span class="keyword">return</span> getDelegate();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (isGetPropertyCall(invocation)) &#123;</span><br><span class="line">            <span class="keyword">return</span> getProperty(getNameArgument(invocation));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> invocation.proceed();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> String <span class="title">getNameArgument</span><span class="params">(MethodInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (String) invocation.getArguments()[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">isGetDelegateCall</span><span class="params">(MethodInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> invocation.getMethod().getName().equals(<span class="string">"getDelegate"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">isRefreshCall</span><span class="params">(MethodInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> invocation.getMethod().getName().equals(<span class="string">"refresh"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">isGetPropertyCall</span><span class="params">(MethodInvocation invocation)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> invocation.getMethod().getName().equals(<span class="string">"getProperty"</span>)</span><br><span class="line">                &amp;&amp; invocation.getMethod().getParameters().length == <span class="number">1</span></span><br><span class="line">                &amp;&amp; invocation.getMethod().getParameters()[<span class="number">0</span>].getType() == String.class;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>重点讲两个地方：</p>
<ol>
<li>如果是调用<strong>refresh</strong>方法，则调用父类<strong>CachingDelegateEncryptablePropertySource.refresh()</strong>。</li>
<li>如果是调用<strong>getProperty</strong>方法，调用父类<strong>CachingDelegateEncryptablePropertySource.getProperty(String name)</strong>。</li>
</ol>
<p>看来所有的核心方法都在<strong>CachingDelegateEncryptablePropertySource</strong>，直接上这个类的代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CachingDelegateEncryptablePropertySource</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">PropertySource</span>&lt;<span class="title">T</span>&gt; <span class="keyword">implements</span> <span class="title">EncryptablePropertySource</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> PropertySource&lt;T&gt; delegate;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EncryptablePropertyResolver resolver;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> EncryptablePropertyFilter filter;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; cache;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CachingDelegateEncryptablePropertySource</span><span class="params">(PropertySource&lt;T&gt; delegate, EncryptablePropertyResolver resolver, EncryptablePropertyFilter filter)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>(delegate.getName(), delegate.getSource());</span><br><span class="line">        Assert.notNull(delegate, <span class="string">"PropertySource delegate cannot be null"</span>);</span><br><span class="line">        Assert.notNull(resolver, <span class="string">"EncryptablePropertyResolver cannot be null"</span>);</span><br><span class="line">        Assert.notNull(filter, <span class="string">"EncryptablePropertyFilter cannot be null"</span>);</span><br><span class="line">        <span class="keyword">this</span>.delegate = delegate;</span><br><span class="line">        <span class="keyword">this</span>.resolver = resolver;</span><br><span class="line">        <span class="keyword">this</span>.filter = filter;</span><br><span class="line">        <span class="keyword">this</span>.cache = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> PropertySource&lt;T&gt; <span class="title">getDelegate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> delegate;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getProperty</span><span class="params">(String name)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// Can be called recursively, so, we cannot use computeIfAbsent.</span></span><br><span class="line">        <span class="keyword">if</span> (cache.containsKey(name)) &#123;</span><br><span class="line">            <span class="keyword">return</span> cache.get(name);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!cache.containsKey(name)) &#123;</span><br><span class="line">                Object resolved = getProperty(resolver, filter, delegate, name);</span><br><span class="line">                <span class="keyword">if</span> (resolved != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    cache.put(name, resolved);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> cache.get(name);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">refresh</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        cache.clear();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这是一个自带缓存的PropertySource代理，逻辑主要是先看<strong>cache</strong>有没有该key，如果没有则从目标对象的PropertySource中获取，获取到了放到<strong>cache</strong>中，下一次可以直接从缓存里面拿。这下终于弄清楚了，原来导致nacos值无法动态更新的罪魁祸首在这里。</p>
<h3 id="解决问题"><a href="#解决问题" class="headerlink" title="解决问题"></a>解决问题</h3><p>&emsp;&emsp;<strong>jasypt-spring-boot-starter</strong>导致nacos的@NacosValue注解无法动态更新值的问题，网上目前好像还没有好的解决方案，我只在<strong>jasypt-spring-boot</strong>这个项目中看到一个尚在Open状态的issue：<a href="https://github.com/ulisesbocchio/jasypt-spring-boot/issues/186" target="_blank" rel="noopener">nacos autorefresh feature not working with jasypt #186</a>，看来只能先自己解决一下了，贴一下我的nacos和jasypt版本：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>nacos-config-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.2.7<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.github.ulisesbocchio<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jasypt-spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.0.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>我的思路是既然<strong>CachingDelegateEncryptablePropertySource</strong>的cache缓存导致我们每次propertySource.getProperty(key)拿到的不是最新的值，那么就不妨在nacos更新配置后刷新下cache缓存，而且<strong>CachingDelegateEncryptablePropertySource</strong>也很贴心地准备了<strong>refresh()</strong>方法，那么我们可以在接收到<strong>NacosConfigReceivedEvent</strong>事件的时候刷新下cache：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EncryptablePropertySourceCacheRefresher</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ConfigurableEnvironment environment;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EncryptablePropertySourceCacheRefresher</span><span class="params">(ConfigurableEnvironment environment)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.environment = environment;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">refreshCachedProperties</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        PropertySources propertySources = environment.getPropertySources();</span><br><span class="line">        propertySources.forEach(<span class="keyword">this</span>::refreshPropertySource);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@SuppressWarnings</span>(<span class="string">"rawtypes"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">refreshPropertySource</span><span class="params">(PropertySource&lt;?&gt; propertySource)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (propertySource <span class="keyword">instanceof</span> CompositePropertySource) &#123;</span><br><span class="line">            CompositePropertySource cps = (CompositePropertySource) propertySource;</span><br><span class="line">            cps.getPropertySources().forEach(<span class="keyword">this</span>::refreshPropertySource);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (propertySource <span class="keyword">instanceof</span> EncryptablePropertySource) &#123;</span><br><span class="line">            EncryptablePropertySource eps = (EncryptablePropertySource) propertySource;</span><br><span class="line">            eps.refresh();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@EventListener</span>(NacosConfigReceivedEvent.class)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onApplicationEvent</span><span class="params">(NacosConfigReceivedEvent event)</span> </span>&#123;</span><br><span class="line">        refreshCachedProperties();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@EventListener</span>(ApplicationReadyEvent.class)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onApplicationEvent</span><span class="params">(ApplicationReadyEvent event)</span> </span>&#123;</span><br><span class="line">        refreshCachedProperties();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>对于<strong>NacosConfigReceivedEvent</strong>事件，因为无法保证我的onApplicationEvent监听先于nacos的监听执行，所以在<strong>ApplicationReadyEvent</strong>事件之后再清除下缓存，保证项目启动之后配置再次更新不会拿到老的值。不过这个解决方案仅仅是兼容而已（并不完美），希望nacos或者jasypt能够尽快解决吧。</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/nacos/" rel="tag"># nacos</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2020/11/26/谈谈Spring的AOP/" rel="next" title="谈谈Spring的AOP">
                <i class="fa fa-chevron-left"></i> 谈谈Spring的AOP
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2020/12/03/记一次nacos刷新失败排查（二）/" rel="prev" title="记一次nacos刷新失败排查（二）">
                记一次nacos刷新失败排查（二） <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
      <div id="disqus_thread">
        <noscript>
          Please enable JavaScript to view the
          <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a>
        </noscript>
      </div>
    </div>

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">matrix</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives">
              
                  <span class="site-state-item-count">47</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">18</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                
                  <span class="site-state-item-count">19</span>
                  <span class="site-state-item-name">标签</span>
                
              </div>
            

          </nav>

          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#nacos源码"><span class="nav-number">1.</span> <span class="nav-text">nacos源码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#多版本"><span class="nav-number">2.</span> <span class="nav-text">多版本</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#jasypt-spring-boot-starter"><span class="nav-number">3.</span> <span class="nav-text">jasypt-spring-boot-starter</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#解决问题"><span class="nav-number">4.</span> <span class="nav-text">解决问题</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">matrix</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Pisces</a> v5.1.4</div>
<div class=BbeiAn-info">
  浙ICP备 -
  <a href="http://www.beian.miit.gov.cn">18040498号</a>
  </a>
</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  

    
      <script id="dsq-count-scr" src="https://luyun.disqus.com/count.js" async></script>
    

    
      <script type="text/javascript">
        var disqus_config = function () {
          this.page.url = 'http://yoursite.com/2020/11/29/记一次nacos刷新失败排查/';
          this.page.identifier = '2020/11/29/记一次nacos刷新失败排查/';
          this.page.title = '记一次nacos刷新失败排查';
        };
        var d = document, s = d.createElement('script');
        s.src = 'https://luyun.disqus.com/embed.js';
        s.setAttribute('data-timestamp', '' + +new Date());
        (d.head || d.body).appendChild(s);
      </script>
    

  




	





  














  





  

  

  

  
  

  

  

  

</body>
</html>
